﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SharedGenomics.Core.Collections
{
    public class TreeNode<T>
    {
        private readonly T _value;
        private readonly IEqualityComparer<T> _comparer;
        private readonly List<TreeNode<T>> _childNodes;

        public TreeNode(T value)
            : this(value, null, null)
        {
        }

        public TreeNode(T value, IEqualityComparer<T> comparer)
            : this(value, comparer, null)
        {
        }

        public TreeNode(T value, IEqualityComparer<T> comparer, IEnumerable<TreeNode<T>> children)
        {
            this._value = value;
            this._comparer = comparer ?? EqualityComparer<T>.Default;

            this._childNodes = new List<TreeNode<T>>();
            this._childNodes.AddRange(children ?? Enumerable.Empty<TreeNode<T>>());
        }

        public void AddChild(TreeNode<T> child)
        {
            child.ThrowIfNull("child");
            this._childNodes.Add(child);
        }

        public TreeNode<T> AddChild(T value)
        {
            TreeNode<T> child = new TreeNode<T>(value, this._comparer);
            this.AddChild(child);
            return child;
        }

        public TreeNode<T> GetChildByIndex(int index)
        {
            index.ThrowIfNotBetween(0, this._childNodes.Count - 1, true, "index");
            return this._childNodes[index];
        }

        public TreeNode<T> GetChildByValue(T value)
        {
            return this._childNodes.FirstOrDefault(node => this._comparer.Equals(node.Value, value));
        }

        public TreeNode<T> RemoveChildByIndex(int index)
        {
            index.ThrowIfNotBetween(0, this._childNodes.Count - 1, true, "index");

            var nodeToRemove = this._childNodes[index];
            this._childNodes.RemoveAt(index);
            return nodeToRemove;
        }

        public TreeNode<T> RemoveChildByValue(T value)
        {
            int nodeIndex = this._childNodes.FindIndex(node => this._comparer.Equals(node.Value, value));
            return nodeIndex == -1
                ? null
                : this.RemoveChildByIndex(nodeIndex);
        }

        public bool ValueEquals(T value)
        {
            return this._comparer.Equals(this._value, value);
        }

        public void SetChildOrder(IList<int> orderMap)
        {
            orderMap.ThrowIfNull("orderMap");
            ExceptionHelper.ThrowIf<ArgumentException>(orderMap.Count != this._childNodes.Count, String.Format("Order map wrong size - expected {0}", this._childNodes.Count));
            ExceptionHelper.ThrowIf<ArgumentException>(!orderMap.All(index => index.IsBetween(0, this._childNodes.Count - 1, true)), "Order map contains invalid index");

            var newChildList = orderMap.Select(i => this._childNodes[i]).ToList();

            this._childNodes.Clear();
            this._childNodes.AddRange(newChildList);
        }

        public IEnumerable<TreeNode<T>> Children
        {
            get { return this._childNodes.AsEnumerable(); }
        }

        public IEnumerable<TreeNode<T>> SubTreeNodes
        {
            get
            {
                return new[] { this }.Concat(this._childNodes.SelectMany(node => node.SubTreeNodes));
            }
        }

        public T Value
        {
            get { return this._value; }
        }

        public bool Visited { get; set; }

        public int ChildCount
        {
            get { return this._childNodes.Count; }
        }
    }
}
